// 基于 SnowLang Project (https://gitee.com/jcnc-org/snow) 的衍生作品
// 原始版权归属: Copyright © 2025 Ke Xu (Luke)
// 本实现版权归属: Copyright © 2025 Sany
// 遵循 Apache License 2.0 授权
package engine

import (
	"fmt"
	"strconv"
	"strings"

	"gitee.com/xntk2381/cc/vm/execution"
	"gitee.com/xntk2381/cc/vm/module"
	"gitee.com/xntk2381/cc/vm/utils"
)

const (
	PROGRAM_END = int(^uint(0) >> 1) // 最大int值，相当于Java的Integer.MAX_VALUE
	HALT        = -1
)

/**
 * Virtual-Machine Engine ({@code VirtualMachineEngine})
 *
 * <p>Interprets and executes a list of VM instructions while maintaining
 * a program counter (PC) and the runtime data structures required for
 * operand manipulation and method invocation.</p>
 *
 * <ul>
 *   <li>{@link OperandStack} — stores intermediate values</li>
 *   <li>{@link LocalVariableStore} — holds locals for the <em>current</em>
 *       stack frame</li>
 *   <li>{@link CallStack} — manages stack frames and return addresses</li>
 *   <li>{@link CommandExecutionHandler} — dispatches opcodes</li>
 * </ul>
 *
 * Root-frame contract:
 * <p>
 * A <strong>root stack frame</strong> is pushed <em>once</em> via
 * {@link #ensureRootFrame()} before the first instruction executes
 * and is never popped.  When a {@code RET} executed in the root frame
 * returns {@link #PROGRAM_END}, the main loop exits gracefully.
 */
type VirtualMachineEngine struct {
	operandStack            *module.OperandStack
	localVariableStore      *module.LocalVariableStore
	callStack               *module.CallStack
	commandExecutionHandler *execution.CommandExecutionHandler
	programCounter          int
}

func NewVirtualMachineEngine(vmMode module.VMMode) *VirtualMachineEngine {
	vme := &VirtualMachineEngine{
		operandStack:       module.NewOperandStack(),
		callStack:          module.NewCallStack(),
		localVariableStore: module.NewLocalVariableStore(vmMode),
		programCounter:     0,
	}
	vme.commandExecutionHandler = execution.NewCommandExecutionHandler(
		vme.operandStack,
		vme.localVariableStore,
		vme.callStack,
	)
	return vme
}

func (vm *VirtualMachineEngine) Execute(program []string) error {
	if len(program) == 0 {
		return fmt.Errorf("the command list cannot be empty or null")
	}

	vm.ensureRootFrame()

	for {
		if vm.programCounter == PROGRAM_END {
			break
		}

		if vm.programCounter < 0 || vm.programCounter >= len(program) {
			break
		}

		rawLine := program[vm.programCounter]
		if rawLine == "" || rawLine[0] == '#' {
			vm.programCounter++
			continue
		}

		parts := strings.Fields(rawLine)
		if len(parts) < 1 {
			utils.LogError(fmt.Sprintf("Invalid command format at PC=%d -> Missing opcode", vm.programCounter))
			break
		}

		opCode, err := vm.parseOpCode(parts[0])
		if err != nil {
			utils.LogError(fmt.Sprintf("Command error at PC=%d -> %v", vm.programCounter, err))
			break
		}

		nextPC, err := vm.commandExecutionHandler.Handle(opCode, parts, vm.programCounter)
		if err != nil {
			utils.LogError(fmt.Sprintf("Command error at PC=%d -> %v", vm.programCounter, err))
			break
		}

		if nextPC == HALT || nextPC == PROGRAM_END {
			vm.programCounter = PROGRAM_END
			continue
		}

		if nextPC == vm.programCounter {
			vm.programCounter++
		} else {
			vm.programCounter = nextPC
		}
	}

	if !vm.callStack.IsEmpty() {
		frm, err := vm.callStack.PeekFrame()
		if err != nil {
			utils.LogError(fmt.Sprintf("Error peeking frame: %v", err))
			return err
		}

		rootLvs := frm.GetLocalVariableStore()
		rootLvs.Compact()
	}

	return nil
}

func (vm *VirtualMachineEngine) ensureRootFrame() {
	if !vm.callStack.IsEmpty() {
		return
	}

	rootCtx := module.NewMethodContext("root", nil)
	rootFrame := module.NewStackFrame(PROGRAM_END, vm.localVariableStore, rootCtx)
	vm.callStack.PushFrame(rootFrame)
}

func (vm *VirtualMachineEngine) PrintStack() {
	vm.operandStack.PrintOperandStack()
	vm.callStack.PrintCallStack()
}

func (vm *VirtualMachineEngine) PrintLocalVariables() {
	if vm.callStack.IsEmpty() {
		utils.LogInfo("Local Variables", "Local variable table is empty")
		return
	}
	frm, err := vm.callStack.PeekFrame()
	if err != nil {
		utils.LogError(fmt.Sprintf("Error peeking frame: %v", err))
		return
	}

	frm.GetLocalVariableStore().PrintLv()
}

func (vm *VirtualMachineEngine) parseOpCode(opCodeStr string) (int, error) {
	return strconv.Atoi(opCodeStr)
}
